<!DOCTYPE html>
<html>

<head>
  <title>OptaPlanner - Using AI to optimize a schedule with OptaPlanner</title>
  <script id="adobe_dtm" src="https://www.redhat.com/dtm.js" type="text/javascript"></script>
  <script src="/assets/javascript/highlight.pack.js" type="text/javascript"></script>
  <META HTTP-EQUIV='Content-Security-Policy' CONTENT="default-src 'none'; script-src 'self' 'unsafe-eval' 'sha256-ANpuoVzuSex6VhqpYgsG25OHWVA1I+F6aGU04LoI+5s=' 'sha256-ipy9P/3rZZW06mTLAR0EnXvxSNcnfSDPLDuh3kzbB1w=' js.bizographics.com https://www.redhat.com assets.adobedtm.com jsonip.com https://ajax.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com https://use.fontawesome.com; style-src 'self' https://fonts.googleapis.com https://use.fontawesome.com; img-src 'self' *; media-src 'self' ; frame-src https://www.googletagmanager.com https://www.youtube.com; frame-ancestors 'none'; base-uri 'none'; object-src 'none'; form-action 'none'; font-src 'self' https://use.fontawesome.com https://fonts.gstatic.com;">
  <META HTTP-EQUIV='X-Frame-Options' CONTENT="DENY">
  <META HTTP-EQUIV='X-XSS-Protection' CONTENT="1; mode=block">
  <META HTTP-EQUIV='X-Content-Type-Options' CONTENT="nosniff">
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="Quarkus: Supersonic Subatomic Java">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@QuarkusIO"> 
  <meta name="twitter:creator" content="@QuarkusIO">
  <meta property="og:url" content="https://quarkus.io/guides/optaplanner" />
  <meta property="og:title" content="OptaPlanner - Using AI to optimize a schedule with OptaPlanner" />
  <meta property="og:description" content="Quarkus: Supersonic Subatomic Java" />
  <meta property="og:image" content="/assets/images/quarkus_card.png" />
  <link rel="canonical" href="https://quarkus.io/guides/optaplanner">
  <link rel="shortcut icon" type="image/png" href="/favicon.ico" >
  <link rel="stylesheet" href="https://quarkus.io/guides/stylesheet/config.css" />
  <link rel="stylesheet" href="/assets/css/main.css" />
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
  <link rel="alternate" type="application/rss+xml"  href="https://quarkus.io/feed.xml" title="Quarkus">
  <script src="https://quarkus.io/assets/javascript/goan.js" type="text/javascript"></script>
  <script src="https://quarkus.io/assets/javascript/hl.js" type="text/javascript"></script>
</head>

<body class="guides">
  <!-- Google Tag Manager (noscript) -->
  <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-NJWS5L"
  height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
  <!-- End Google Tag Manager (noscript) -->

  <div class="nav-wrapper">
  <div class="grid-wrapper">
    <div class="width-12-12">
      <input type="checkbox" id="checkbox" />
      <nav id="main-nav" class="main-nav">
  <div class="container">
    <div class="logo-wrapper">
      
        <a href="/"><img src="/assets/images/quarkus_logo_horizontal_rgb_600px_reverse.png" class="project-logo" title="Quarkus"></a>
      
    </div>
    <label class="nav-toggle" for="checkbox">
      <i class="fa fa-bars"></i>
    </label>
    <div id="menu" class="menu">
      <span>
        <a href="/get-started/" class="">Get Started</a>
      </span>
      <span>
        <a href="/guides/" class="active">Guides</a>
      </span>
      <span>
        <a href="/community/" class="">Community</a>
      </span>
      <span>
        <a href="/support/" class="">Support</a>
      </span>
      <span>
        <a href="/blog/" class="">Blog</a>
      </span>
      <span>
        <a href="https://code.quarkus.io" class="button-cta secondary white">Start Coding</a>
      </span>
    </div>
  </div>
      </nav>
    </div>
  </div>
</div>

  <div class="content">
    <div class="guide">
  <div class="width-12-12">
    <h1 class="text-caps">OptaPlanner - Using AI to optimize a schedule with OptaPlanner</h1>
    <div class="hide-mobile toc"><ul class="sectlevel1">
<li><a href="#what-you-will-build">What you will build</a></li>
<li><a href="#solution">Solution</a></li>
<li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#the-build-file-and-the-dependencies">The build file and the dependencies</a></li>
<li><a href="#model-the-domain-objects">Model the domain objects</a>
<ul class="sectlevel2">
<li><a href="#timeslot">Timeslot</a></li>
<li><a href="#room">Room</a></li>
<li><a href="#lesson">Lesson</a></li>
</ul>
</li>
<li><a href="#define-the-constraints-and-calculate-the-score">Define the constraints and calculate the score</a></li>
<li><a href="#gather-the-domain-objects-in-a-planning-solution">Gather the domain objects in a planning solution</a>
<ul class="sectlevel2">
<li><a href="#the-value-range-providers">The value range providers</a></li>
<li><a href="#the-problem-fact-and-planning-entity-properties">The problem fact and planning entity properties</a></li>
</ul>
</li>
<li><a href="#create-the-solver-service">Create the solver service</a></li>
<li><a href="#set-the-termination-time">Set the termination time</a></li>
<li><a href="#make-the-application-executable">Make the application executable</a>
<ul class="sectlevel2">
<li><a href="#try-the-application">Try the application</a></li>
<li><a href="#test-the-application">Test the application</a></li>
<li><a href="#logging">Logging</a></li>
</ul>
</li>
<li><a href="#summary">Summary</a></li>
<li><a href="#further-improvements-database-and-ui-integration">Further improvements: Database and UI integration</a></li>
</ul></div>
    <div>
      <div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>This guide walks you through the process of creating a Quarkus application
with <a href="https://www.optaplanner.org/">OptaPlanner</a>'s constraint solving Artificial Intelligence (AI).</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="what-you-will-build"><a class="anchor" href="#what-you-will-build"></a>What you will build</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You will build a REST application that optimizes a school timetable for students and teachers:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/guides/images/optaplanner-time-table-app-screenshot.png" alt="optaplanner time table app screenshot">
</div>
</div>
<div class="paragraph">
<p>Your service will assign <code>Lesson</code> instances to <code>Timeslot</code> and <code>Room</code> instances automatically
by using AI to adhere to hard and soft scheduling <em>constraints</em>, such as:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>A room can have at most one lesson at the same time.</p>
</li>
<li>
<p>A teacher can teach at most one lesson at the same time.</p>
</li>
<li>
<p>A student can attend at most one lesson at the same time.</p>
</li>
<li>
<p>A teacher prefers to teach in a single room.</p>
</li>
<li>
<p>A teacher prefers to teach sequential lessons and dislikes gaps between lessons.</p>
</li>
<li>
<p>A student dislikes sequential lessons on the same subject.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Mathematically speaking, school timetabling is an <em>NP-hard</em> problem.
That means it is difficult to scale.
Simply brute force iterating through all possible combinations takes millions of years
for a non-trivial dataset, even on a supercomputer.
Luckily, AI constraint solvers such as OptaPlanner have advanced algorithms
that deliver a near-optimal solution in a reasonable amount of time.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="solution"><a class="anchor" href="#solution"></a>Solution</h2>
<div class="sectionbody">
<div class="paragraph">
<p>We recommend that you follow the instructions in the next sections and create the application step by step.
However, you can go right to the completed example.</p>
</div>
<div class="paragraph">
<p>Clone the Git repository: <code>git clone <a href="https://github.com/quarkusio/quarkus-quickstarts.git" class="bare">https://github.com/quarkusio/quarkus-quickstarts.git</a></code>, or download an <a href="https://github.com/quarkusio/quarkus-quickstarts/archive/master.zip">archive</a>.</p>
</div>
<div class="paragraph">
<p>The solution is located in <a href="https://github.com/quarkusio/quarkus-quickstarts/tree/master/optaplanner-quickstart">the <code>optaplanner-quickstart</code> directory</a>.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="prerequisites"><a class="anchor" href="#prerequisites"></a>Prerequisites</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To complete this guide, you need:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>about 30 minutes</p>
</li>
<li>
<p>an IDE</p>
</li>
<li>
<p>JDK 1.8+ installed with <code>JAVA_HOME</code> configured appropriately</p>
</li>
<li>
<p>Apache Maven 3.6.2+ or Gradle 4+</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="the-build-file-and-the-dependencies"><a class="anchor" href="#the-build-file-and-the-dependencies"></a>The build file and the dependencies</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Use <a href="https://code.quarkus.io/">code.quarkus.io</a> to generate an application
with the following extensions, for Maven or Gradle:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>RESTEasy JAX-RS (<code>quarkus-resteasy</code>)</p>
</li>
<li>
<p>RESTEasy Jackson (<code>quarkus-resteasy-jackson</code>)</p>
</li>
<li>
<p>OptaPlanner (<code>optaplanner-quarkus</code>)</p>
</li>
<li>
<p>OptaPlanner Jackson (<code>optaplanner-quarkus-jackson</code>)</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Alternatively, generate it from the command line with Maven:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-none hljs">mvn io.quarkus:quarkus-maven-plugin:1.7.0.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=optaplanner-quickstart \
    -Dextensions="resteasy, resteasy-jackson, optaplanner-quarkus, optaplanner-quarkus-jackson"
cd optaplanner-quickstart</code></pre>
</div>
</div>
<div class="paragraph">
<p>In Maven, your <code>pom.xml</code> file contains these dependencies:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="xml" class="language-xml hljs">  &lt;dependencyManagement&gt;
    &lt;dependencies&gt;
      &lt;dependency&gt;
        &lt;groupId&gt;io.quarkus&lt;/groupId&gt;
        &lt;artifactId&gt;quarkus-universe-bom&lt;/artifactId&gt;
        &lt;version&gt;1.7.0.Final&lt;/version&gt;
        &lt;type&gt;pom&lt;/type&gt;
        &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
  &lt;/dependencyManagement&gt;
  &lt;dependencies&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.quarkus&lt;/groupId&gt;
      &lt;artifactId&gt;quarkus-resteasy&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.quarkus&lt;/groupId&gt;
      &lt;artifactId&gt;quarkus-resteasy-jackson&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.optaplanner&lt;/groupId&gt;
      &lt;artifactId&gt;optaplanner-quarkus&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.optaplanner&lt;/groupId&gt;
      &lt;artifactId&gt;optaplanner-quarkus-jackson&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
      &lt;groupId&gt;io.quarkus&lt;/groupId&gt;
      &lt;artifactId&gt;quarkus-junit5&lt;/artifactId&gt;
      &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="model-the-domain-objects"><a class="anchor" href="#model-the-domain-objects"></a>Model the domain objects</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Your goal is to assign each lesson to a time slot and a room.
You will create these classes:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/guides/images/optaplanner-time-table-class-diagram-pure.png" alt="optaplanner time table class diagram pure">
</div>
</div>
<div class="sect2">
<h3 id="timeslot"><a class="anchor" href="#timeslot"></a>Timeslot</h3>
<div class="paragraph">
<p>The <code>Timeslot</code> class represents a time interval when lessons are taught,
for example, <code>Monday 10:30 - 11:30</code> or <code>Tuesday 13:30 - 14:30</code>.
For simplicity&#8217;s sake, all time slots have the same duration
and there are no time slots during lunch or other breaks.</p>
</div>
<div class="paragraph">
<p>A time slot has no date, because a high school schedule just repeats every week.
So there is no need for <a href="https://docs.optaplanner.org/latestFinal/optaplanner-docs/html_single/index.html#continuousPlanning">continuous planning</a>.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/main/java/org/acme/optaplanner/domain/Timeslot.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.domain;

import java.time.DayOfWeek;
import java.time.LocalTime;

public class Timeslot {

    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;

    public Timeslot() {
    }

    public Timeslot(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) {
        this.dayOfWeek = dayOfWeek;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    public DayOfWeek getDayOfWeek() {
        return dayOfWeek;
    }

    public LocalTime getStartTime() {
        return startTime;
    }

    public LocalTime getEndTime() {
        return endTime;
    }

    @Override
    public String toString() {
        return dayOfWeek + " " + startTime;
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Because no <code>Timeslot</code> instances change during solving, a <code>Timeslot</code> is called a <em>problem fact</em>.
Such classes do not require any OptaPlanner specific annotations.</p>
</div>
<div class="paragraph">
<p>Notice the <code>toString()</code> method keeps the output short,
so it is easier to read OptaPlanner&#8217;s <code>DEBUG</code> or <code>TRACE</code> log, as shown later.</p>
</div>
</div>
<div class="sect2">
<h3 id="room"><a class="anchor" href="#room"></a>Room</h3>
<div class="paragraph">
<p>The <code>Room</code> class represents a location where lessons are taught,
for example, <code>Room A</code> or <code>Room B</code>.
For simplicity&#8217;s sake, all rooms are without capacity limits
and they can accommodate all lessons.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/main/java/org/acme/optaplanner/domain/Room.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.domain;

public class Room {

    private String name;

    public Room() {
    }

    public Room(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p><code>Room</code> instances do not change during solving, so <code>Room</code> is also a <em>problem fact</em>.</p>
</div>
</div>
<div class="sect2">
<h3 id="lesson"><a class="anchor" href="#lesson"></a>Lesson</h3>
<div class="paragraph">
<p>During a lesson, represented by the <code>Lesson</code> class,
a teacher teaches a subject to a group of students,
for example, <code>Math by A.Turing for 9th grade</code> or <code>Chemistry by M.Curie for 10th grade</code>.
If a subject is taught multiple times per week by the same teacher to the same student group,
there are multiple <code>Lesson</code> instances that are only distinguishable by <code>id</code>.
For example, the 9th grade has six math lessons a week.</p>
</div>
<div class="paragraph">
<p>During solving, OptaPlanner changes the <code>timeslot</code> and <code>room</code> fields of the <code>Lesson</code> class,
to assign each lesson to a time slot and a room.
Because OptaPlanner changes these fields, <code>Lesson</code> is a <em>planning entity</em>:</p>
</div>
<div class="imageblock">
<div class="content">
<img src="/guides/images/optaplanner-time-table-class-diagram-annotated.png" alt="optaplanner time table class diagram annotated">
</div>
</div>
<div class="paragraph">
<p>Most of the fields in the previous diagram contain input data, except for the orange fields:
A lesson&#8217;s <code>timeslot</code> and <code>room</code> fields are unassigned (<code>null</code>) in the input data
and assigned (not <code>null</code>) in the output data.
OptaPlanner changes these fields during solving.
Such fields are called planning variables.
In order for OptaPlanner to recognize them,
both the <code>timeslot</code> and <code>room</code> fields require an <code>@PlanningVariable</code> annotation.
Their containing class, <code>Lesson</code>, requires an <code>@PlanningEntity</code> annotation.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/main/java/org/acme/optaplanner/domain/Lesson.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.domain;

import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.variable.PlanningVariable;

@PlanningEntity
public class Lesson {

    @PlanningId
    private Long id;

    private String subject;
    private String teacher;
    private String studentGroup;

    @PlanningVariable(valueRangeProviderRefs = "timeslotRange")
    private Timeslot timeslot;
    @PlanningVariable(valueRangeProviderRefs = "roomRange")
    private Room room;

    public Lesson() {
    }

    public Lesson(Long id, String subject, String teacher, String studentGroup) {
        this.id = id;
        this.subject = subject;
        this.teacher = teacher;
        this.studentGroup = studentGroup;
    }

    public Long getId() {
        return id;
    }

    public String getSubject() {
        return subject;
    }

    public String getTeacher() {
        return teacher;
    }

    public String getStudentGroup() {
        return studentGroup;
    }

    public Timeslot getTimeslot() {
        return timeslot;
    }

    public void setTimeslot(Timeslot timeslot) {
        this.timeslot = timeslot;
    }

    public Room getRoom() {
        return room;
    }

    public void setRoom(Room room) {
        this.room = room;
    }

    @Override
    public String toString() {
        return subject + "(" + id + ")";
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>Lesson</code> class has an <code>@PlanningEntity</code> annotation,
so OptaPlanner knows that this class changes during solving
because it contains one or more planning variables.</p>
</div>
<div class="paragraph">
<p>The <code>timeslot</code> field has an <code>@PlanningVariable</code> annotation,
so OptaPlanner knows that it can change its value.
In order to find potential <code>Timeslot</code> instances to assign to this field,
OptaPlanner uses the <code>valueRangeProviderRefs</code> property to connect to a value range provider
(explained later) that provides a <code>List&lt;Timeslot&gt;</code> to pick from.</p>
</div>
<div class="paragraph">
<p>The <code>room</code> field also has an <code>@PlanningVariable</code> annotation, for the same reasons.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
<div class="paragraph">
<p>Determining the <code>@PlanningVariable</code> fields for an arbitrary constraint solving use case
is often challenging the first time.
Read <a href="https://docs.optaplanner.org/latestFinal/optaplanner-docs/html_single/index.html#domainModelingGuide">the domain modeling guidelines</a>
to avoid common pitfalls.</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="define-the-constraints-and-calculate-the-score"><a class="anchor" href="#define-the-constraints-and-calculate-the-score"></a>Define the constraints and calculate the score</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A <em>score</em> represents the quality of a given solution.
The higher the better.
OptaPlanner looks for the best solution, which is the solution with the highest score found in the available time.
It could be the <em>optimal</em> solution.</p>
</div>
<div class="paragraph">
<p>Because this use case has hard and soft constraints,
use the <code>HardSoftScore</code> class to represent the score:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Hard constraints must not be broken. For example: <em>A room can have at most one lesson at the same time.</em></p>
</li>
<li>
<p>Soft constraints should not be broken. For example: <em>A teacher prefers to teach in a single room.</em></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Hard constraints are weighted against other hard constraints.
Soft constraints are weighted too, against other soft constraints.
<strong>Hard constraints always outweigh soft constraints</strong>, regardless of their respective weights.</p>
</div>
<div class="paragraph">
<p>To calculate the score, you could implement an <code>EasyScoreCalculator</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">public class TimeTableEasyScoreCalculator implements EasyScoreCalculator&lt;TimeTable&gt; {

    @Override
    public HardSoftScore calculateScore(TimeTable timeTable) {
        List&lt;Lesson&gt; lessonList = timeTable.getLessonList();
        int hardScore = 0;
        for (Lesson a : lessonList) {
            for (Lesson b : lessonList) {
                if (a.getTimeslot() != null &amp;&amp; a.getTimeslot().equals(b.getTimeslot())
                        &amp;&amp; a.getId() &lt; b.getId()) {
                    // A room can accommodate at most one lesson at the same time.
                    if (a.getRoom() != null &amp;&amp; a.getRoom().equals(b.getRoom())) {
                        hardScore--;
                    }
                    // A teacher can teach at most one lesson at the same time.
                    if (a.getTeacher().equals(b.getTeacher())) {
                        hardScore--;
                    }
                    // A student can attend at most one lesson at the same time.
                    if (a.getStudentGroup().equals(b.getStudentGroup())) {
                        hardScore--;
                    }
                }
            }
        }
        int softScore = 0;
        // Soft constraints are only implemented in optaplanner-quickstart
        return HardSoftScore.of(hardScore, softScore);
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Unfortunately <strong>that does not scale well</strong>, because it is non-incremental:
every time a lesson is assigned to a different time slot or room,
all lessons are re-evaluated to calculate the new score.</p>
</div>
<div class="paragraph">
<p>Instead, create a <code>src/main/java/org/acme/optaplanner/solver/TimeTableConstraintProvider.java</code> class
to perform incremental score calculation.
It uses OptaPlanner&#8217;s ConstraintStream API which is inspired by Java 8 Streams and SQL:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.solver;

import org.acme.optaplanner.domain.Lesson;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
import org.optaplanner.core.api.score.stream.ConstraintProvider;
import org.optaplanner.core.api.score.stream.Joiners;

public class TimeTableConstraintProvider implements ConstraintProvider {

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[] {
                // Hard constraints
                roomConflict(constraintFactory),
                teacherConflict(constraintFactory),
                studentGroupConflict(constraintFactory),
                // Soft constraints are only implemented in optaplanner-quickstart
        };
    }

    private Constraint roomConflict(ConstraintFactory constraintFactory) {
        // A room can accommodate at most one lesson at the same time.

        // Select a lesson ...
        return constraintFactory.from(Lesson.class)
                // ... and pair it with another lesson ...
                .join(Lesson.class,
                        // ... in the same timeslot ...
                        Joiners.equal(Lesson::getTimeslot),
                        // ... in the same room ...
                        Joiners.equal(Lesson::getRoom),
                        // ... and the pair is unique (different id, no reverse pairs)
                        Joiners.lessThan(Lesson::getId))
                // then penalize each pair with a hard weight.
                .penalize("Room conflict", HardSoftScore.ONE_HARD);
    }

    private Constraint teacherConflict(ConstraintFactory constraintFactory) {
        // A teacher can teach at most one lesson at the same time.
        return constraintFactory
                .fromUniquePair(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getTeacher))
                .penalize("Teacher conflict", HardSoftScore.ONE_HARD);
    }

    private Constraint studentGroupConflict(ConstraintFactory constraintFactory) {
        // A student can attend at most one lesson at the same time.
        return constraintFactory
                .fromUniquePair(Lesson.class,
                        Joiners.equal(Lesson::getTimeslot),
                        Joiners.equal(Lesson::getStudentGroup))
                .penalize("Student group conflict", HardSoftScore.ONE_HARD);
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>ConstraintProvider</code> scales an order of magnitude better than the <code>EasyScoreCalculator</code>: <em>O</em>(n) instead of <em>O</em>(n²).</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="gather-the-domain-objects-in-a-planning-solution"><a class="anchor" href="#gather-the-domain-objects-in-a-planning-solution"></a>Gather the domain objects in a planning solution</h2>
<div class="sectionbody">
<div class="paragraph">
<p>A <code>TimeTable</code> wraps all <code>Timeslot</code>, <code>Room</code>, and <code>Lesson</code> instances of a single dataset.
Furthermore, because it contains all lessons, each with a specific planning variable state,
it is a <em>planning solution</em> and it has a score:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>If lessons are still unassigned, then it is an <em>uninitialized</em> solution,
for example, a solution with the score <code>-4init/0hard/0soft</code>.</p>
</li>
<li>
<p>If it breaks hard constraints, then it is an <em>infeasible</em> solution,
for example, a solution with the score <code>-2hard/-3soft</code>.</p>
</li>
<li>
<p>If it adheres to all hard constraints, then it is a <em>feasible</em> solution,
for example, a solution with the score <code>0hard/-7soft</code>.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Create the <code>src/main/java/org/acme/optaplanner/domain/TimeTable.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.domain;

import java.util.List;

import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;

@PlanningSolution
public class TimeTable {

    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "timeslotRange")
    private List&lt;Timeslot&gt; timeslotList;
    @ProblemFactCollectionProperty
    @ValueRangeProvider(id = "roomRange")
    private List&lt;Room&gt; roomList;
    @PlanningEntityCollectionProperty
    private List&lt;Lesson&gt; lessonList;

    @PlanningScore
    private HardSoftScore score;

    public TimeTable() {
    }

    public TimeTable(List&lt;Timeslot&gt; timeslotList, List&lt;Room&gt; roomList, List&lt;Lesson&gt; lessonList) {
        this.timeslotList = timeslotList;
        this.roomList = roomList;
        this.lessonList = lessonList;
    }

    public List&lt;Timeslot&gt; getTimeslotList() {
        return timeslotList;
    }

    public List&lt;Room&gt; getRoomList() {
        return roomList;
    }

    public List&lt;Lesson&gt; getLessonList() {
        return lessonList;
    }

    public HardSoftScore getScore() {
        return score;
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>The <code>TimeTable</code> class has an <code>@PlanningSolution</code> annotation,
so OptaPlanner knows that this class contains all of the input and output data.</p>
</div>
<div class="paragraph">
<p>Specifically, this class is the input of the problem:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>A <code>timeslotList</code> field with all time slots</p>
<div class="ulist">
<ul>
<li>
<p>This is a list of problem facts, because they do not change during solving.</p>
</li>
</ul>
</div>
</li>
<li>
<p>A <code>roomList</code> field with all rooms</p>
<div class="ulist">
<ul>
<li>
<p>This is a list of problem facts, because they do not change during solving.</p>
</li>
</ul>
</div>
</li>
<li>
<p>A <code>lessonList</code> field with all lessons</p>
<div class="ulist">
<ul>
<li>
<p>This is a list of planning entities, because they change during solving.</p>
</li>
<li>
<p>Of each <code>Lesson</code>:</p>
<div class="ulist">
<ul>
<li>
<p>The values of the <code>timeslot</code> and <code>room</code> fields are typically still <code>null</code>, so unassigned.
They are planning variables.</p>
</li>
<li>
<p>The other fields, such as <code>subject</code>, <code>teacher</code> and <code>studentGroup</code>, are filled in.
These fields are problem properties.</p>
</li>
</ul>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div class="paragraph">
<p>However, this class is also the output of the solution:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>A <code>lessonList</code> field for which each <code>Lesson</code> instance has non-null <code>timeslot</code> and <code>room</code> fields after solving</p>
</li>
<li>
<p>A <code>score</code> field that represents the quality of the output solution, for example, <code>0hard/-5soft</code></p>
</li>
</ul>
</div>
<div class="sect2">
<h3 id="the-value-range-providers"><a class="anchor" href="#the-value-range-providers"></a>The value range providers</h3>
<div class="paragraph">
<p>That <code>timeslotList</code> field is a value range provider.
It holds the <code>Timeslot</code> instances which OptaPlanner can pick from to assign to the <code>timeslot</code> field of <code>Lesson</code> instances.
The <code>timeslotList</code> field has an <code>@ValueRangeProvider</code> annotation to connect those two,
by matching the <code>id</code> with the <code>valueRangeProviderRefs</code> of the <code>@PlanningVariable</code> in the <code>Lesson</code>.</p>
</div>
<div class="paragraph">
<p>Following the same logic, the <code>roomList</code> field also has an <code>@ValueRangeProvider</code> annotation.</p>
</div>
</div>
<div class="sect2">
<h3 id="the-problem-fact-and-planning-entity-properties"><a class="anchor" href="#the-problem-fact-and-planning-entity-properties"></a>The problem fact and planning entity properties</h3>
<div class="paragraph">
<p>Furthermore, OptaPlanner needs to know which <code>Lesson</code> instances it can change
as well as how to retrieve the <code>Timeslot</code> and <code>Room</code> instances used for score calculation
by your <code>TimeTableConstraintProvider</code>.</p>
</div>
<div class="paragraph">
<p>The <code>timeslotList</code> and <code>roomList</code> fields have an <code>@ProblemFactCollectionProperty</code> annotation,
so your <code>TimeTableConstraintProvider</code> can select <em>from</em> those instances.</p>
</div>
<div class="paragraph">
<p>The <code>lessonList</code> has an <code>@PlanningEntityCollectionProperty</code> annotation,
so OptaPlanner can change them during solving
and your <code>TimeTableConstraintProvider</code> can select <em>from</em> those too.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="create-the-solver-service"><a class="anchor" href="#create-the-solver-service"></a>Create the solver service</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now you are ready to put everything together and create a REST service.
But solving planning problems on REST threads causes HTTP timeout issues.
Therefore, the Quarkus extension injects a <code>SolverManager</code>,
which runs solvers in a separate thread pool
and can solve multiple datasets in parallel.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/main/java/org/acme/optaplanner/rest/TimeTableResource.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.rest;

import java.util.UUID;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.acme.optaplanner.domain.TimeTable;
import org.optaplanner.core.api.solver.SolverJob;
import org.optaplanner.core.api.solver.SolverManager;

@Path("/timeTable")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TimeTableResource {

    @Inject
    SolverManager&lt;TimeTable, UUID&gt; solverManager;

    @POST
    @Path("/solve")
    public TimeTable solve(TimeTable problem) {
        UUID problemId = UUID.randomUUID();
        // Submit the problem to start solving
        SolverJob&lt;TimeTable, UUID&gt; solverJob = solverManager.solve(problemId, problem);
        TimeTable solution;
        try {
            // Wait until the solving ends
            solution = solverJob.getFinalBestSolution();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException("Solving failed.", e);
        }
        return solution;
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>For simplicity&#8217;s sake, this initial implementation waits for the solver to finish,
which can still cause an HTTP timeout.
The <em>complete</em> implementation avoids HTTP timeouts much more elegantly.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="set-the-termination-time"><a class="anchor" href="#set-the-termination-time"></a>Set the termination time</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Without a termination setting or a termination event, the solver runs forever.
To avoid that, limit the solving time to five seconds.
That is short enough to avoid the HTTP timeout.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/main/resources/application.properties</code> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="properties" class="language-properties hljs"># The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
# It's recommended to run for at least 5 minutes ("5m") otherwise.
quarkus.optaplanner.solver.termination.spent-limit=5s</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="make-the-application-executable"><a class="anchor" href="#make-the-application-executable"></a>Make the application executable</h2>
<div class="sectionbody">
<div class="paragraph">
<p>First start the application:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="shell" class="language-shell hljs">$ ./mvnw compile quarkus:dev</code></pre>
</div>
</div>
<div class="sect2">
<h3 id="try-the-application"><a class="anchor" href="#try-the-application"></a>Try the application</h3>
<div class="paragraph">
<p>Now that the application is running, you can test the REST service.
You can use any REST client you wish.
The following example uses the Linux command <code>curl</code> to send a POST request:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="shell" class="language-shell hljs">$ curl -i -X POST http://localhost:8080/timeTable/solve -H "Content-Type:application/json" -d '{"timeslotList":[{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"}],"roomList":[{"name":"Room A"},{"name":"Room B"}],"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade"},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade"},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade"},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade"}]}'</code></pre>
</div>
</div>
<div class="paragraph">
<p>After about five seconds, according to the termination spent time defined in your <code>application.properties</code>,
the service returns an output similar to the following example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-none hljs">HTTP/1.1 200
Content-Type: application/json
...

{"timeslotList":...,"roomList":...,"lessonList":[{"id":1,"subject":"Math","teacher":"A. Turing","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room A"}},{"id":2,"subject":"Chemistry","teacher":"M. Curie","studentGroup":"9th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room A"}},{"id":3,"subject":"French","teacher":"M. Curie","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"08:30:00","endTime":"09:30:00"},"room":{"name":"Room B"}},{"id":4,"subject":"History","teacher":"I. Jones","studentGroup":"10th grade","timeslot":{"dayOfWeek":"MONDAY","startTime":"09:30:00","endTime":"10:30:00"},"room":{"name":"Room B"}}],"score":"0hard/0soft"}</code></pre>
</div>
</div>
<div class="paragraph">
<p>Notice that your application assigned all four lessons to one of the two time slots and one of the two rooms.
Also notice that it conforms to all hard constraints.
For example, M. Curie&#8217;s two lessons are in different time slots.</p>
</div>
<div class="paragraph">
<p>On the server side, the <code>info</code> log show what OptaPlanner did in those five seconds:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-none hljs">... Solving started: time spent (33), best score (-8init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
... Construction Heuristic phase (0) ended: time spent (73), best score (0hard/0soft), score calculation speed (459/sec), step total (4).
... Local Search phase (1) ended: time spent (5000), best score (0hard/0soft), score calculation speed (28949/sec), step total (28398).
... Solving ended: time spent (5000), best score (0hard/0soft), score calculation speed (28524/sec), phase total (2), environment mode (REPRODUCIBLE).</code></pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="test-the-application"><a class="anchor" href="#test-the-application"></a>Test the application</h3>
<div class="paragraph">
<p>A good application includes test coverage.
In a JUnit test, generate a test dataset and send it to the <code>TimeTableResource</code> to solve.</p>
</div>
<div class="paragraph">
<p>Create the <code>src/test/java/org/acme/optaplanner/rest/TimeTableResourceTest.java</code> class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.rest;

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.Timeslot;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.rest.TimeTableResource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@QuarkusTest
public class TimeTableResourceTest {

    @Inject
    TimeTableResource timeTableResource;

    @Test
    @Timeout(600_000)
    public void solve() {
        TimeTable problem = generateProblem();
        TimeTable solution = timeTableResource.solve(problem);
        assertFalse(solution.getLessonList().isEmpty());
        for (Lesson lesson : solution.getLessonList()) {
            assertNotNull(lesson.getTimeslot());
            assertNotNull(lesson.getRoom());
        }
        assertTrue(solution.getScore().isFeasible());
    }

    private TimeTable generateProblem() {
        List&lt;Timeslot&gt; timeslotList = new ArrayList&lt;&gt;();
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(8, 30), LocalTime.of(9, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(9, 30), LocalTime.of(10, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(10, 30), LocalTime.of(11, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(13, 30), LocalTime.of(14, 30)));
        timeslotList.add(new Timeslot(DayOfWeek.MONDAY, LocalTime.of(14, 30), LocalTime.of(15, 30)));

        List&lt;Room&gt; roomList = new ArrayList&lt;&gt;();
        roomList.add(new Room("Room A"));
        roomList.add(new Room("Room B"));
        roomList.add(new Room("Room C"));

        List&lt;Lesson&gt; lessonList = new ArrayList&lt;&gt;();
        lessonList.add(new Lesson(101L, "Math", "B. May", "9th grade"));
        lessonList.add(new Lesson(102L, "Physics", "M. Curie", "9th grade"));
        lessonList.add(new Lesson(103L, "Geography", "M. Polo", "9th grade"));
        lessonList.add(new Lesson(104L, "English", "I. Jones", "9th grade"));
        lessonList.add(new Lesson(105L, "Spanish", "P. Cruz", "9th grade"));

        lessonList.add(new Lesson(201L, "Math", "B. May", "10th grade"));
        lessonList.add(new Lesson(202L, "Chemistry", "M. Curie", "10th grade"));
        lessonList.add(new Lesson(203L, "History", "I. Jones", "10th grade"));
        lessonList.add(new Lesson(204L, "English", "P. Cruz", "10th grade"));
        lessonList.add(new Lesson(205L, "French", "M. Curie", "10th grade"));
        return new TimeTable(timeslotList, roomList, lessonList);
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>This test verifies that after solving, all lessons are assigned to a time slot and a room.
It also verifies that it found a feasible solution (no hard constraints broken).</p>
</div>
<div class="paragraph">
<p>Add test properties to the <code>src/main/resources/application.properties</code> file:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="properties" class="language-properties hljs"># The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation.
# It's recommended to run for at least 5 minutes ("5m") otherwise.
quarkus.optaplanner.solver.termination.spent-limit=5s

# Effectively disable this termination in favor of the best-score-limit
%test.quarkus.optaplanner.solver.termination.spent-limit=1h
%test.quarkus.optaplanner.solver.termination.best-score-limit=0hard/*soft</code></pre>
</div>
</div>
<div class="paragraph">
<p>Normally, the solver finds a feasible solution in less than 200 milliseconds.
Notice how the <code>application.properties</code> overwrites the solver termination during tests
to terminate as soon as a feasible solution (<code>0hard/*soft</code>) is found.
This avoids hard coding a solver time, because the unit test might run on arbitrary hardware.
This approach ensures that the test runs long enough to find a feasible solution, even on slow machines.
But it does not run a millisecond longer than it strictly must, even on fast machines.</p>
</div>
</div>
<div class="sect2">
<h3 id="logging"><a class="anchor" href="#logging"></a>Logging</h3>
<div class="paragraph">
<p>When adding constraints in your <code>ConstraintProvider</code>,
keep an eye on the <em>score calculation speed</em> in the <code>info</code> log,
after solving for the same amount of time, to assess the performance impact:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-none hljs">... Solving ended: ..., score calculation speed (29455/sec), ...</code></pre>
</div>
</div>
<div class="paragraph">
<p>To understand how OptaPlanner is solving your problem internally,
change the logging in the <code>application.properties</code> file or with a <code>-D</code> system property:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="properties" class="language-properties hljs">quarkus.log.category."org.optaplanner".level=debug</code></pre>
</div>
</div>
<div class="paragraph">
<p>Use <code>debug</code> logging to show every <em>step</em>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-none hljs">... Solving started: time spent (67), best score (-20init/0hard/0soft), environment mode (REPRODUCIBLE), random (JDK with seed 0).
...     CH step (0), time spent (128), score (-18init/0hard/0soft), selected move count (15), picked move ([Math(101) {null -&gt; Room A}, Math(101) {null -&gt; MONDAY 08:30}]).
...     CH step (1), time spent (145), score (-16init/0hard/0soft), selected move count (15), picked move ([Physics(102) {null -&gt; Room A}, Physics(102) {null -&gt; MONDAY 09:30}]).
...</code></pre>
</div>
</div>
<div class="paragraph">
<p>Use <code>trace</code> logging to show every <em>step</em> and every <em>move</em> per step.</p>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="summary"><a class="anchor" href="#summary"></a>Summary</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Congratulations!
You have just developed a Quarkus application with <a href="https://www.optaplanner.org/">OptaPlanner</a>!</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="further-improvements-database-and-ui-integration"><a class="anchor" href="#further-improvements-database-and-ui-integration"></a>Further improvements: Database and UI integration</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Now try adding database and UI integration:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Store <code>Timeslot</code>, <code>Room</code>, and <code>Lesson</code> in the database with <a href="https://quarkus.io/guides/hibernate-orm-panache">Hibernate and Panache</a>.</p>
</li>
<li>
<p><a href="https://quarkus.io/guides/rest-json">Expose them through REST</a>.</p>
</li>
<li>
<p>Adjust the <code>TimeTableResource</code> to read and write a <code>TimeTable</code> in a single transaction
and use those accordingly:</p>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.rest;

import javax.inject.Inject;
import javax.transaction.Transactional;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import io.quarkus.panache.common.Sort;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.Room;
import org.acme.optaplanner.domain.TimeTable;
import org.acme.optaplanner.domain.Timeslot;
import org.optaplanner.core.api.score.ScoreManager;
import org.optaplanner.core.api.solver.SolverManager;
import org.optaplanner.core.api.solver.SolverStatus;

@Path("/timeTable")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TimeTableResource {

    public static final Long SINGLETON_TIME_TABLE_ID = 1L;

    @Inject
    SolverManager&lt;TimeTable, Long&gt; solverManager;
    @Inject
    ScoreManager&lt;TimeTable&gt; scoreManager;

    // To try, open http://localhost:8080/timeTable
    @GET
    public TimeTable getTimeTable() {
        // Get the solver status before loading the solution
        // to avoid the race condition that the solver terminates between them
        SolverStatus solverStatus = getSolverStatus();
        TimeTable solution = findById(SINGLETON_TIME_TABLE_ID);
        scoreManager.updateScore(solution); // Sets the score
        solution.setSolverStatus(solverStatus);
        return solution;
    }

    @POST
    @Path("/solve")
    public void solve() {
        solverManager.solveAndListen(SINGLETON_TIME_TABLE_ID,
                this::findById,
                this::save);
    }

    public SolverStatus getSolverStatus() {
        return solverManager.getSolverStatus(SINGLETON_TIME_TABLE_ID);
    }

    @POST
    @Path("/stopSolving")
    public void stopSolving() {
        solverManager.terminateEarly(SINGLETON_TIME_TABLE_ID);
    }

    @Transactional
    protected TimeTable findById(Long id) {
        if (!SINGLETON_TIME_TABLE_ID.equals(id)) {
            throw new IllegalStateException("There is no timeTable with id (" + id + ").");
        }
        // Occurs in a single transaction, so each initialized lesson references the same timeslot/room instance
        // that is contained by the timeTable's timeslotList/roomList.
        return new TimeTable(
                Timeslot.listAll(Sort.by("dayOfWeek").and("startTime").and("endTime").and("id")),
                Room.listAll(Sort.by("name").and("id")),
                Lesson.listAll(Sort.by("subject").and("teacher").and("studentGroup").and("id")));
    }

    @Transactional
    protected void save(TimeTable timeTable) {
        for (Lesson lesson : timeTable.getLessonList()) {
            // TODO this is awfully naive: optimistic locking causes issues if called by the SolverManager
            Lesson attachedLesson = Lesson.findById(lesson.getId());
            attachedLesson.setTimeslot(lesson.getTimeslot());
            attachedLesson.setRoom(lesson.getRoom());
        }
    }

}</code></pre>
</div>
</div>
<div class="paragraph">
<p>For simplicity&#8217;s sake, this code handles only one <code>TimeTable</code>,
but it is straightforward to enable multi-tenancy and handle multiple <code>TimeTable</code> instances of different high schools in parallel.</p>
</div>
<div class="paragraph">
<p>The <code>getTimeTable()</code> method returns the latest time table from the database.
It uses the <code>ScoreManager</code> (which is automatically injected)
to calculate the score of that time table, so the UI can show the score.</p>
</div>
<div class="paragraph">
<p>The <code>solve()</code> method starts a job to solve the current time table and store the time slot and room assignments in the database.
It uses the <code>SolverManager.solveAndListen()</code> method to listen to intermediate best solutions
and update the database accordingly.
This enables the UI to show progress while the backend is still solving.</p>
</div>
</li>
<li>
<p>Adjust the <code>TimeTableResourceTest</code> accordingly, now that the <code>solve()</code> method returns immediately.
Poll for the latest solution until the solver finishes solving:</p>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code data-lang="java" class="language-java hljs">package org.acme.optaplanner.rest;

import javax.inject.Inject;

import io.quarkus.test.junit.QuarkusTest;
import org.acme.optaplanner.domain.Lesson;
import org.acme.optaplanner.domain.TimeTable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.optaplanner.core.api.solver.SolverStatus;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@QuarkusTest
public class TimeTableResourceTest {

    @Inject
    TimeTableResource timeTableResource;

    @Test
    @Timeout(600_000)
    public void solveDemoDataUntilFeasible() throws InterruptedException {
        timeTableResource.solve();
        TimeTable timeTable = timeTableResource.getTimeTable();
        while (timeTable.getSolverStatus() != SolverStatus.NOT_SOLVING) {
            // Quick polling (not a Test Thread Sleep anti-pattern)
            // Test is still fast on fast machines and doesn't randomly fail on slow machines.
            Thread.sleep(20L);
            timeTable = timeTableResource.getTimeTable();
        }
        assertFalse(timeTable.getLessonList().isEmpty());
        for (Lesson lesson : timeTable.getLessonList()) {
            assertNotNull(lesson.getTimeslot());
            assertNotNull(lesson.getRoom());
        }
        assertTrue(timeTable.getScore().isFeasible());
    }

}</code></pre>
</div>
</div>
</li>
<li>
<p>Build an attractive web UI on top of these REST methods to visualize the timetable.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>Take a look at <a href="#solution">the solution</a> to see how this all turns out.</p>
</div>
</div>
</div>
    </div>
  </div>
</div>

  </div>

  <div class="content project-footer">
  <div class="footer-section">
    <div class="logo-wrapper">
      <a href="/"><img src="/assets/images/quarkus_logo_horizontal_rgb_reverse.svg" class="project-logo" title="Quarkus"></a>
    </div>
  </div>
  <div class="grid-wrapper">
    <p class="grid__item width-3-12">Quarkus is open. All dependencies of this project are available under the <a href='https://www.apache.org/licenses/LICENSE-2.0' target='_blank'>Apache Software License 2.0</a> or compatible license.<br /><br />This website was built with <a href='https://jekyllrb.com/' target='_blank'>Jekyll</a>, is hosted on <a href='https://pages.github.com/' target='_blank'>Github Pages</a> and is completely open source. If you want to make it better, <a href='https://github.com/quarkusio/quarkusio.github.io' target='_blank'>fork the website</a> and show us what you’ve got.</p>

    
      <div class="width-1-12 project-links">
        <span>Navigation</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="/">Home</a></li>
          
            <li><a href="/guides">Guides</a></li>
          
            <li><a href="/community/#contributing">Contribute</a></li>
          
            <li><a href="/faq">FAQ</a></li>
          
            <li><a href="/get-started">Get Started</a></li>
          
        </ul>
      </div>
    
      <div class="width-1-12 project-links">
        <span>Contribute</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="https://twitter.com/quarkusio">Follow us</a></li>
          
            <li><a href="https://github.com/quarkusio">GitHub</a></li>
          
            <li><a href="/security">Security&nbsp;policy</a></li>
          
        </ul>
      </div>
    
      <div class="width-1-12 project-links">
        <span>Get Help</span>
        <ul class="footer-links width-1-12">
          
            <li><a href="https://groups.google.com/forum/#!forum/quarkus-dev">Forums</a></li>
          
            <li><a href="https://quarkusio.zulipchat.com">Chatroom</a></li>
          
        </ul>
      </div>
    

    
      <div class="width-3-12 more-links">
        <span>Quarkus is made of community projects</span>
        <ul class="footer-links">
          
            <li><a href="https://vertx.io/" target="_blank">Eclipse Vert.x</a></li>
          
            <li><a href="https://microprofile.io" target="_blank">Eclipse MicroProfile</a></li>
          
            <li><a href="https://hibernate.org" target="_blank">Hibernate</a></li>
          
            <li><a href="https://netty.io" target="_blank">Netty</a></li>
          
            <li><a href="https://resteasy.github.io" target="_blank">RESTEasy</a></li>
          
            <li><a href="https://camel.apache.org" target="_blank">Apache Camel</a></li>
          
            <li><a href="https://code.quarkus.io/" target="_blank">And many more...</a></li>
          
        </ul>
      </div>
    
  </div>
</div>
  <div class="content redhat-footer">
  <div class="grid-wrapper">
    <span class="licence">
      <i class="fab fa-creative-commons"></i><i class="fab fa-creative-commons-by"></i> <a href="https://creativecommons.org/licenses/by/3.0/" target="_blank">CC by 3.0</a> | <a href="https://www.redhat.com/en/about/privacy-policy">Privacy Policy</a>
    </span>
    <span class="redhat">
      Sponsored by
    </span>
    <span class="redhat-logo">
      <a href="https://www.redhat.com/" target="_blank"><img src="/assets/images/redhat_reversed.svg"></a>
    </span>
  </div>
</div>


  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js" integrity="sha384-8gBf6Y4YYq7Jx97PIqmTwLPin4hxIzQw5aDmUg/DDhul9fFpbbLcLh3nTIIDJKhx" crossorigin="anonymous"></script>
  <script type="text/javascript" src="/assets/javascript/mobile-nav.js"></script>
  <script type="text/javascript" src="/assets/javascript/scroll-down.js"></script>
  <script src="/assets/javascript/satellite.js" type="text/javascript"></script>
  <script src="https://quarkus.io/guides/javascript/config.js" type="text/javascript"></script>
  <script src="/assets/javascript/search-filter.js" type="text/javascript"></script>
  <script src="/assets/javascript/back-to-top.js" type="text/javascript"></script>
</body>

</html>
